Skip to content

migrate to C++20 - #507

Merged
nschimme merged 1 commit into
MUME:masterfrom
nschimme:c++20
Apr 7, 2026
Merged

migrate to C++20#507
nschimme merged 1 commit into
MUME:masterfrom
nschimme:c++20

Conversation

@nschimme

@nschimme nschimme commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Migrate the project to C++20 and adopt C++20 language/library features while tightening type constraints and constexpr usage.

Enhancements:

  • Replace custom utilities and workarounds with C++20 equivalents such as std::erase_if, std::remove_cvref_t, std::source_location, constrained templates, and std::integral auto parameters.
  • Refine utility and helper abstractions (e.g., TaggedInt underlying_helper, TinyRoomIdSet conversion, Array deduction guides, AnsiOstream::write overloads, and various CRTP helpers) to use C++20 requires-clauses and cleaner interfaces.
  • Improve constexpr floating‑point utilities by leveraging constexpr cmath or compiler builtins where available and simplifying NaN/finite checks for portability.
  • Tighten type safety and correctness in several components, including emoji encoding, UTF‑8 helpers, numeric hashing, and syntax Value vector accessors.

Build:

  • Raise the global and target C++ standard from C++17 to C++20 for the main targets and all tests, and adjust Clang/MSVC warning and conformance flags accordingly.

Documentation:

  • Update the build documentation to require a C++20-capable compiler with specific minimum versions.

Tests:

  • Update test target properties to build with C++20 and modern constexpr-based helpers where used.

@sourcery-ai

sourcery-ai Bot commented Apr 7, 2026

Copy link
Copy Markdown

Reviewer's Guide

Migrates the project and tests to C++20 and modernizes several utilities and templates to use C++20 features (concepts/requires, std::erase_if, std::remove_cvref_t, std::source_location, etc.), while tightening some APIs and fixing minor portability/constexpr issues.

Class diagram for updated C++20 utility types and helpers

classDiagram
    class Vector {
        +Vector()
        +Vector(Base &&x)
        +Base::const_iterator begin() const
        +Base::const_iterator end() const
        +bool empty() const
        +size_t size() const
        +const Value &at(size_t pos) const
        +const Value &operator[](size_t pos) const
    }

    class Vector_Base {
        <<typedef>>
    }

    Vector_Base <.. Vector : Base

    class Array_T_N {
        T[N] data
        +Array(First &&first, Types &&...args)
    }

    class std_array_T_N {
    }

    Array_T_N --|> std_array_T_N

    class TaggedInt_underlying_helper_T {
        <<template>>
        +type
    }

    class AnsiOstream {
        -std::ostream &m_os
        +write(char16_t codepoint)
        +write(char32_t codepoint)
        +write(std::string_view sv)
        +write(std::u8string_view sv)
        +write(T n)
    }

    class MakeQPointer {
        <<function template>>
        +makeQPointer(T, Args...)
    }

    class QObject {
    }

    class QPointer_T {
    }

    MakeQPointer ..> QPointer_T : returns
    MakeQPointer ..> QObject : requires base of

    class numeric_hash_fn {
        +size_t numeric_hash(std::integral auto val)
    }

    class mm_source_location_alias {
        <<using>>
        +source_location = std::source_location
    }

    class FloatPredicates {
        +constexpr bool isNan(FloatType f)
        +constexpr bool isFinite(FloatType f)
    }

    class Utils_templates {
        +bool listRemoveIf(std::list<T> &list, Predicate should_remove)
        +remove_cvref_t<T>
    }

    class Charset_Utf8_helpers {
        +constexpr bool is7bit(char c)
        +constexpr bool is7bit(std::string_view sv)
    }

    class Array_deduction_guide {
        <<deduction guide>>
        +Array(T, U...) -> Array<T, 1 + sizeof...(U)>
    }

    Array_deduction_guide ..> Array_T_N

    class OstreamDiffReporter {
        +void printEnum(E x)
    }

    class MakeQPointer_constraints {
        <<concept-style requires>>
    }

    MakeQPointer_constraints ..> MakeQPointer

    class TinyRoomIdSet_convertTo {
        +T convertTo(const U &input)
    }

    class Textures_typeHack {
        +EnumIndexedArray typeHack(const T &input)
    }

    class RawExit_InvariantsHelper {
        +void enforce() requires(not std::is_const_v<Exit_>)
    }
Loading

File-Level Changes

Change Details Files
Adopt C++20 language standard across main target and all tests and adjust compiler flags/docs accordingly.
  • Set CMAKE_CXX_STANDARD from 17 to 20 for the main project and all CMake targets, including tests.
  • For Clang builds, add -Wno-c++20-compat to reduce noise and relax the comment about [[nodiscard]] being C++17-specific.
  • For MSVC builds, enable the conforming preprocessor with /Zc:preprocessor.
  • Update BUILD.md to require a C++20-capable compiler and give concrete minimum versions.
CMakeLists.txt
src/CMakeLists.txt
tests/CMakeLists.txt
BUILD.md
Use C++20 standard library facilities and concepts in global utilities and templates.
  • Replace custom is_nan/is_finite constexpr workarounds with std::isnan/std::isfinite when constexpr cmath is available, falling back to builtins or f!=f where needed.
  • Switch listRemoveIf to use std::erase_if and replace custom remove_cvref_t alias with std::remove_cvref_t.
  • Modernize numeric_hash to take std::integral auto instead of an enable_if-constrained template.
  • Constrain multiple helpers (Array constructor and deduction guide, convertTo in TinyRoomIdSet, underlying_helper specializations, makeQPointer, typeHack, Diff::printEnum) using requires clauses instead of std::enable_if_t.
  • Use std::all_of in a constexpr context for UTF‑8 7‑bit detection.
  • Use std::invoke + constexpr lambdas for CURRENT_PLATFORM/CURRENT_ENVIRONMENT and HideQDebugOptions constants to get well-formed constant initialization.
src/global/float_cast.h
src/global/utils.h
src/global/hash.h
src/map/TinyRoomIdSet.cpp
src/global/TaggedInt.h
src/global/Array.h
src/display/Textures.h
src/global/MakeQPointer.h
src/map/Diff.h
src/global/Charset-Utf8.cpp
src/global/ConfigConsts-Computed.h
tests/TestGlobal.cpp
Adopt std::source_location and simplify custom source location abstraction.
  • Remove the custom mm::source_location struct and its preprocessor-based fallback.
  • Always include <source_location>, alias mm::source_location to std::source_location, and define MM_SOURCE_LOCATION() in terms of std::source_location::current().
src/global/mm_source_location.h
API tightening and minor behavior/portability fixes.
  • Make Vector’s default constructor explicit and move several inline methods (empty, size, at, operator[]) from the header into the implementation file, preserving semantics but allowing separate definition and annotations.
  • Add NODISCARD to comparator overloads in Emojis::HexPrefixTree, remove unused generic unordered_map implementation, and add an explicit cast in snprintf when printing char32_t to avoid format/width issues.
  • Make CRTP mixin base classes trivially constructible by removing protected default constructors and rely on defaulted compiler behavior.
  • Update HelpFrame::makeChild to return HelpFrame{*this} directly (explicit copy-ctor) instead of copying to a named local first.
  • Remove obsolete comments tied to pre-C++20 workarounds and clarify diagnostics around requires clauses and compiler behavior.
src/syntax/Value.h
src/syntax/Value.cpp
src/global/emojis.cpp
src/map/Crtp.h
src/map/RawExit.cpp
src/syntax/TreeParser.cpp
src/global/Charset.cpp
Extend AnsiOstream and related interfaces for C++20 char8_t and refine numeric overload selection.
  • Add a write(std::u8string_view) overload that forwards to the existing std::string_view implementation by reinterpreting underlying bytes.
  • Replace the SFINAE-based write(T) numeric overload with a C++20 constrained template using requires to allow integral types larger than char and floating-point types while excluding char16_t/char32_t.
  • Minor related comment and type tweaks to align with C++20 usage.
src/global/AnsiOstream.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The change to numeric_hash from a std::enable_if_t<std::is_arithmetic_v<T>> template to numeric_hash(const std::integral auto) drops support for floating-point types; if this was not intentional, consider either restoring arithmetic support or adding a separate overload for floats.
  • In AnsiOstream::write, the new requires clause now explicitly excludes char16_t (previously only char32_t was excluded); please double-check whether writing char16_t via this overload is still desired and, if so, adjust the constraint accordingly.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The change to `numeric_hash` from a `std::enable_if_t<std::is_arithmetic_v<T>>` template to `numeric_hash(const std::integral auto)` drops support for floating-point types; if this was not intentional, consider either restoring arithmetic support or adding a separate overload for floats.
- In `AnsiOstream::write`, the new `requires` clause now explicitly excludes `char16_t` (previously only `char32_t` was excluded); please double-check whether writing `char16_t` via this overload is still desired and, if so, adjust the constraint accordingly.

## Individual Comments

### Comment 1
<location path="src/global/MakeQPointer.h" line_range="15-16" />
<code_context>
 template<typename T, typename... Args>
-NODISCARD auto makeQPointer(Args &&...args)
-    -> std::enable_if_t<std::is_base_of_v<QObject, T>, QPointer<T>>
+    requires(std::is_base_of_v<QObject, T>)
+NODISCARD QPointer<T> makeQPointer(Args &&...args)
 {
     auto ptr = std::make_unique<T>(std::forward<Args>(args)...);
</code_context>
<issue_to_address>
**issue (bug_risk):** makeQPointer destroys the newly created QObject before returning, leaving QPointer dangling

This constructs a `std::unique_ptr<T>` and returns a `QPointer<T>` to `ptr.get()`. When `makeQPointer` returns, `ptr` is destroyed and `T` is deleted, so the `QPointer` immediately dangles. You need to transfer ownership out of the `unique_ptr` (e.g. `ptr.release()`) or construct with `new T(...)` so Qt owns the object via the parent hierarchy and it survives the function scope.
</issue_to_address>

### Comment 2
<location path="src/global/hash.h" line_range="11" />
<code_context>
-template<typename T>
-MAYBE_UNUSED NODISCARD static auto numeric_hash(const T val) noexcept
-    -> std::enable_if_t<std::is_arithmetic_v<T>, size_t>
+MAYBE_UNUSED NODISCARD static size_t numeric_hash(const std::integral auto val) noexcept
 {
-    static constexpr const size_t size = sizeof(val);
</code_context>
<issue_to_address>
**question (bug_risk):** numeric_hash now only accepts integral types, which is a behavior change from the previous arithmetic version

Previously, `std::is_arithmetic_v<T>` allowed both integral and floating-point arguments; `const std::integral auto val` now rejects floats, so existing callers hashing `float`/`double` will break. If this narrowing is intentional, please document or rename accordingly; otherwise, consider a `requires std::is_arithmetic_v<T>` constraint or adding a `std::floating_point` overload to preserve prior behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/global/MakeQPointer.h
Comment on lines +15 to +16
requires(std::is_base_of_v<QObject, T>)
NODISCARD QPointer<T> makeQPointer(Args &&...args)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): makeQPointer destroys the newly created QObject before returning, leaving QPointer dangling

This constructs a std::unique_ptr<T> and returns a QPointer<T> to ptr.get(). When makeQPointer returns, ptr is destroyed and T is deleted, so the QPointer immediately dangles. You need to transfer ownership out of the unique_ptr (e.g. ptr.release()) or construct with new T(...) so Qt owns the object via the parent hierarchy and it survives the function scope.

Comment thread src/global/hash.h
template<typename T>
MAYBE_UNUSED NODISCARD static auto numeric_hash(const T val) noexcept
-> std::enable_if_t<std::is_arithmetic_v<T>, size_t>
MAYBE_UNUSED NODISCARD static size_t numeric_hash(const std::integral auto val) noexcept

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (bug_risk): numeric_hash now only accepts integral types, which is a behavior change from the previous arithmetic version

Previously, std::is_arithmetic_v<T> allowed both integral and floating-point arguments; const std::integral auto val now rejects floats, so existing callers hashing float/double will break. If this narrowing is intentional, please document or rename accordingly; otherwise, consider a requires std::is_arithmetic_v<T> constraint or adding a std::floating_point overload to preserve prior behavior.

@codecov

codecov Bot commented Apr 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 42.85714% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 25.05%. Comparing base (f9515ee) to head (23c8b20).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/syntax/Value.cpp 0.00% 8 Missing ⚠️
src/global/MakeQPointer.h 0.00% 1 Missing ⚠️
src/global/emojis.cpp 75.00% 1 Missing ⚠️
src/map/Diff.h 0.00% 1 Missing ⚠️
src/syntax/TreeParser.cpp 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #507      +/-   ##
==========================================
- Coverage   25.10%   25.05%   -0.05%     
==========================================
  Files         511      510       -1     
  Lines       42289    42275      -14     
  Branches     4574     4574              
==========================================
- Hits        10617    10594      -23     
- Misses      31672    31681       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nschimme
nschimme merged commit 93d43e3 into MUME:master Apr 7, 2026
19 of 20 checks passed
@nschimme
nschimme deleted the c++20 branch April 7, 2026 23:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant